
# 从 qtpy 库导入 QtCore 模块，提供核心的非 GUI 功能
from qtpy import QtCore
# 从 qtpy 库导入 QtGui 模块，提供图形和用户界面相关功能
from qtpy import QtGui
# 从 qtpy.QtWidgets 模块导入 QApplication 类，用于管理 GUI 应用程序
from qtpy.QtWidgets import QApplication
# 从 qtpy.QtWidgets 模块导入 QGraphicsWidget 类，用于创建图形视图框架中的可交互部件
from qtpy.QtWidgets import QGraphicsWidget
# 从 qtpy.QtWidgets 模块导入 QMenu 类，用于创建上下文菜单
from qtpy.QtWidgets import QMenu
# 从 qtpy.QtWidgets 模块导入 QInputDialog 类，用于创建输入对话框
from qtpy.QtWidgets import QInputDialog
# 从 qtpy.QtWidgets 模块导入 QSizePolicy 类，用于管理部件的大小策略
from qtpy.QtWidgets import QSizePolicy

# 从 src.UI.Canvas.Painters 模块导入 PinPainter 类，用于绘制引脚
from src.UI.Canvas.Painters import PinPainter
# 从 src.UI.Canvas.WatchPinValueItem 模块导入 WatchItem 类，用于监控引脚值
from src.UI.Canvas.WatchPinValueItem import WatchItem
# 从 src.UI.Canvas.UICommon 模块导入所有内容，提供 UI 相关的通用功能
from src.UI.Canvas.UICommon import *

# 定义一个全局字典，用于存储 UI 引脚的工厂函数
UI_PINS_FACTORIES = {}


class UIPinBase(QGraphicsWidget):
    """
    UI 引脚包装器类，用于封装和管理 UI 中的引脚。

    该类继承自 QGraphicsWidget，提供了引脚的基本功能，如连接、断开连接、数据设置等，并定义了相关的信号。
    """

    # 事件信号，当引脚连接时发出，参数为连接的对象
    OnPinConnected = QtCore.Signal(object)
    # 事件信号，当引脚断开连接时发出，参数为断开连接的对象
    OnPinDisconnected = QtCore.Signal(object)
    # 事件信号，当数据被设置时发出，参数为设置的数据对象
    dataBeenSet = QtCore.Signal(object)
    # 事件信号，当引脚名称改变时发出，参数为新的显示名称
    displayNameChanged = QtCore.Signal(str)
    # 事件信号，当引脚发生变化时发出，参数为变化的引脚对象
    OnPinChanged = QtCore.Signal(object)
    # 事件信号，当引脚被删除时发出，参数为被删除的引脚对象
    OnPinDeleted = QtCore.Signal(object)
    # 事件信号，当引脚执行时发出，参数为执行的引脚对象
    OnPinExecute = QtCore.Signal(object)

    def __init__(self, owningNode, raw_pin):
        """
        初始化 UI 引脚包装器实例。

        :param owningNode: 拥有该引脚的节点，类型为 PyFlow.UI.Canvas.NodeBase
        :param raw_pin: 核心引脚引用，类型为 PyFlow.Core.PinBase.PinBase
        """

        super(UIPinBase, self).__init__()
        # 设置当前图形项
        self.setGraphicsItem(self)
        # 设置标志，使部件在几何形状改变时发送信号
        self.setFlag(QGraphicsWidget.ItemSendsGeometryChanges)
        # 注释掉的代码，设置缓存模式为设备坐标缓存
        # self.setCacheMode(self.DeviceCoordinateCache)
        # 设置接受悬停事件
        self.setAcceptHoverEvents(True)
        # 设置 Z 值，确定部件在 Z 轴上的绘制顺序
        self.setZValue(1)
        # 设置父项为拥有该引脚的节点
        self.setParentItem(owningNode)

        # 使用弱引用引用拥有该引脚的节点
        self.UiNode = weakref.ref(owningNode)
        # 保存核心引脚引用
        self._rawPin = raw_pin
        # 初始化监控小部件为 None
        self.watchWidget = None
        if self._rawPin is not None:
            # 连接核心引脚的序列化钩子信号到当前对象的序列化钩子方法
            self._rawPin.serializationHook.connect(self.serializationHook)
            # 连接核心引脚的容器类型改变信号到当前对象的容器类型改变处理方法
            self._rawPin.containerTypeChanged.connect(self.onContainerTypeChanged)
            # 初始化显示名称为核心引脚的名称
            self._displayName = self._rawPin.name
            # 设置核心引脚的包装器为当前对象
            self._rawPin.setWrapper(self)
            # 连接核心引脚的销毁信号到当前对象的销毁方法
            self._rawPin.killed.connect(self.kill)
            # 连接核心引脚的名称改变信号到当前对象的设置显示名称方法
            self._rawPin.nameChanged.connect(self.setDisplayName)
            # 连接核心引脚的执行信号到当前对象的核心引脚执行处理方法
            self._rawPin.onExecute.connect(self.on_rawPin_executed)
            # 创建引脚的上下文菜单
            self.menu = QMenu()
            # 为菜单添加 "Rename" 动作，并连接其触发信号到重命名处理方法
            self.menu.addAction("Rename").triggered.connect(self.onRename)
            # 为菜单添加 "Remove" 动作，并连接其触发信号到核心引脚的销毁方法
            self.menu.addAction("Remove").triggered.connect(self._rawPin.kill)
            # 为菜单添加 "Disconnect all" 动作
            self.actionDisconnect = self.menu.addAction("Disconnect all")
            # 连接 "Disconnect all" 动作的触发信号到核心引脚的断开所有连接方法
            self.actionDisconnect.triggered.connect(self._rawPin.disconnectAll)
            # 为菜单添加 "Reset value" 动作
            self.actionResetValue = self.menu.addAction("Reset value")
            # 连接 "Reset value" 动作的触发信号到当前对象的重置为默认值方法
            self.actionResetValue.triggered.connect(self.resetToDefault)
            if self._rawPin._structure == StructureType.Multi:
                # 如果核心引脚的结构类型为 Multi，为菜单添加 "changeStructure" 动作，并连接其触发信号到选择结构方法
                self.menu.addAction("changeStructure").triggered.connect(
                    self.selectStructure
                )
            # 为菜单添加 "Watch" 动作
            self.actionWatchValue = self.menu.addAction("Watch")
            # 连接 "Watch" 动作的触发信号到切换监控值方法
            self.actionWatchValue.triggered.connect(self.toggleWatchValue)
            # 连接核心引脚的数据设置信号到当前对象的更新监控小部件值方法
            self._rawPin.dataBeenSet.connect(self.updateWatchWidgetValue)
            # 为菜单添加 "Copy path" 动作
            self.actionCopyPath = self.menu.addAction("Copy path")
            # 连接 "Copy path" 动作的触发信号到复制路径到剪贴板方法
            self.actionCopyPath.triggered.connect(self.onCopyPathToClipboard)

        # GUI 相关设置
        # 创建一个字体对象，设置字体为 Consolas，字号为 6
        self._font = QtGui.QFont("Consolas")
        self._font.setPointSize(6)
        # 定义引脚的大小
        self.pinSize = 6
        # 标记引脚是否被悬停
        self.hovered = False
        # 标记引脚标签是否隐藏
        self.bLabelHidden = False
        if self._rawPin is not None:
            # 如果核心引脚存在，设置引脚颜色为核心引脚的颜色
            self._pinColor = QtGui.QColor(*self._rawPin.color())
        # 设置标签颜色为白色
        self._labelColor = QtCore.Qt.white
        # 创建执行画笔，颜色为白色，线宽为 0.5，线型为实线
        self._execPen = QtGui.QPen(Colors.White, 0.5, QtCore.Qt.SolidLine)
        # 创建脏数据画笔，颜色为脏数据颜色，线宽为 0.5，线型为虚线，端点样式为圆角，连接样式为圆角
        self._dirty_pen = QtGui.QPen(
            Colors.DirtyPen,
            0.5,
            QtCore.Qt.DashLine,
            QtCore.Qt.RoundCap,
            QtCore.Qt.RoundJoin,
        )

        # 存储 UI 连接列表
        self.uiConnectionList = []

        # 设置大小策略，水平方向为扩展，垂直方向为最大
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum)
        # 定义引脚圆形绘制的偏移量
        self.pinCircleDrawOffset = QtCore.QPointF()
        # TODO: 此检查是为 PinGroup 设计的，需要改进
        if self._rawPin is not None:
            # 如果核心引脚存在，设置工具提示为核心引脚的描述
            self.setToolTip(self._rawPin.description)

    @property
    def wrapperJsonData(self):
        """
        获取包装器的 JSON 数据，通过核心引脚的 wrapperJsonData 属性获取。

        :return: 包装器的 JSON 数据
        """
        return self._rawPin.wrapperJsonData

    def on_rawPin_executed(self, *args, **kwargs):
        """
        处理核心引脚执行事件，发出 OnPinExecute 信号。

        :param args: 可变参数
        :param kwargs: 关键字参数
        """
        self.OnPinExecute.emit(self)

    def onCopyPathToClipboard(self):
        """
        将引脚路径复制到剪贴板。
        先清空剪贴板，然后将引脚路径文本设置到剪贴板。
        """
        QApplication.clipboard().clear()
        QApplication.clipboard().setText(self.path())

    def toggleWatchValue(self):
        """
        切换引脚值的监控状态。
        如果监控小部件已存在，则从场景中移除；否则创建新的监控小部件并添加到场景中。
        """
        if self.watchWidget is not None:
            self.scene().removeItem(self.watchWidget)
            self.watchWidget = None
        else:
            scene = self.owningNode().canvasRef().scene()
            self.watchWidget = WatchItem()
            scene.addItem(self.watchWidget)
            self.watchWidget.setZValue(NodeDefaults().Z_LAYER + 1)
            self.updateWatchWidget()
            self.updateWatchWidgetValue(self.currentData())

    def path(self):
        """
        获取引脚的路径，通过核心引脚的 path 方法获取。

        :return: 引脚的路径
        """
        return self._rawPin.path()

    def updateWatchWidget(self):
        """
        更新监控小部件的位置和可见性。
        根据引脚的方向和拥有节点的可见性设置监控小部件的位置和可见性。
        """
        if self.watchWidget is not None:
            scenePos = (
                self.sceneBoundingRect().bottomLeft()
                if self.direction == PinDirection.Input
                else self.sceneBoundingRect().bottomRight()
            )
            self.watchWidget.setPos(scenePos)
            self.watchWidget.setVisible(self.owningNode().isVisible())

    def updateWatchWidgetValue(self, *args, **kwargs):
        """
        更新监控小部件显示的内容。
        根据引脚的当前数据、结构类型等信息更新监控小部件的文本内容，并更新其位置。

        :param args: 可变参数
        :param kwargs: 关键字参数
        """
        if self.watchWidget is not None:
            content = "Value: {0}".format(str(self.currentData()))
            content += "\nStructure: {0}".format(self._rawPin.structureType.name)
            if self.isAny:
                content += "\nActive data type: {0}".format(self._rawPin.activeDataType)
                content += "\nSuper: {0}".format(self._rawPin.super)
            content += "\nDirty: {0}".format(str(self._rawPin.dirty))
            self.watchWidget.setPlainText(content)
            self.updateWatchWidget()

    def heartBeat(self):
        """
        心跳方法，用于更新监控小部件的位置。
        """
        self.updateWatchWidget()

    def getInputWidgetVariant(self):
        """
        获取输入小部件的变体类型，通过核心引脚的 getInputWidgetVariant 方法获取。

        :return: 输入小部件的变体类型
        """
        return self._rawPin.getInputWidgetVariant()

    @property
    def labelColor(self):
        """
        获取引脚标签的颜色。

        :return: 引脚标签的颜色
        """
        return self._labelColor

    @labelColor.setter
    def labelColor(self, value):
        """
        设置引脚标签的颜色。

        :param value: 要设置的颜色值
        """
        self._labelColor = value

    def pinCenter(self):
        """
        计算引脚圆形绘制的中心点，相对于引脚部件的位置。
        根据引脚的方向、标签是否隐藏以及拥有节点是否折叠等因素计算中心点位置。

        :return: 引脚圆形绘制的中心点位置
        """
        frame = QtCore.QRectF(QtCore.QPointF(0, 0), self.geometry().size())
        halfPinSize = self.pinSize / 2
        pinX = self.pinSize
        pinY = frame.height() / 2
        if not self.bLabelHidden:
            if self.direction == PinDirection.Output:
                pinX = frame.width() - self.pinSize + halfPinSize
        result = QtCore.QPointF(pinX, pinY)
        if self.owningNode().collapsed:
            labelHeight = self.owningNode().labelHeight
            # labelHeight += self.owningNode().nodeLayout.spacing()
            if self.direction == PinDirection.Input:
                result = self.mapFromItem(
                    self.owningNode(), QtCore.QPointF(0, labelHeight)
                )
            if self.direction == PinDirection.Output:
                result = self.mapFromItem(
                    self.owningNode(),
                    QtCore.QPointF(
                        self.owningNode().sizeHint(None, None).width(), labelHeight
                    ),
                )
        return result

    def onContainerTypeChanged(self, *args, **kwargs):
        """
        处理核心引脚容器类型改变事件。
        当引脚的容器类型变为列表或字典时，更新引脚以重新绘制形状。

        :param args: 可变参数
        :param kwargs: 关键字参数
        """
        # underlined pin is changed to list or dict
        # update to redraw shape
        self.update()

    def setLabel(self, labelItem):
        """
        设置引脚标签项。
        仅当 _label 属性为 None 时进行设置，使用弱引用引用标签项。

        :param labelItem: 要设置的标签项
        """
        if self._label is None:
            self._label = weakref.ref(labelItem)

    def displayName(self):
        """
        获取引脚的显示名称。

        :return: 引脚的显示名称
        """
        return self._displayName

    def setDisplayName(self, displayName):
        """
        设置引脚的显示名称。
        如果新的显示名称与当前名称不同，则更新名称，发出显示名称改变信号，并更新部件的几何形状。

        :param displayName: 要设置的显示名称
        """
        if displayName != self._displayName:
            self._displayName = displayName
            self.displayNameChanged.emit(self._displayName)
            self.prepareGeometryChange()
            self.updateGeometry()
            self.update()

    def jsonEncoderClass(self):
        """
        获取 JSON 编码器类，通过核心引脚的 jsonEncoderClass 方法获取。

        :return: JSON 编码器类
        """
        return self._rawPin.jsonEncoderClass()

    def jsonDecoderClass(self):
        """
        获取 JSON 解码器类，通过核心引脚的 jsonDecoderClass 方法获取。

        :return: JSON 解码器类
        """
        return self._rawPin.jsonDecoderClass()

    @property
    def owningNode(self):
        """
        获取拥有该引脚的节点。

        :return: 拥有该引脚的节点的弱引用
        """
        return self.UiNode

    @property
    def constraint(self):
        """
        获取引脚的约束条件，通过核心引脚的 constraint 属性获取。

        :return: 引脚的约束条件
        """
        return self._rawPin.constraint

    @property
    def isAny(self):
        """
        判断引脚是否为任意类型，通过核心引脚的 isAny 方法判断。

        :return: 如果引脚为任意类型返回 True，否则返回 False
        """
        return self._rawPin.isAny()

    def setMenuItemEnabled(self, actionName, bEnabled):
        """
        设置上下文菜单中指定动作的启用状态。
        遍历上下文菜单中的所有动作，找到匹配名称的动作并设置其启用和可见状态。

        :param actionName: 要设置的动作名称
        :param bEnabled: 是否启用该动作
        """
        for action in self.menu.actions():
            if action.text() == actionName:
                if bEnabled != action.isEnabled() and action.isVisible():
                    action.setEnabled(bEnabled)
                    action.setVisible(bEnabled)

    def syncRenamable(self):
        """
        同步引脚重命名功能的启用状态。
        根据核心引脚的重命名选项启用状态，设置上下文菜单中 "Rename" 动作的启用状态。
        """
        renamingEnabled = self._rawPin.optionEnabled(PinOptions.RenamingEnabled)
        # self._label()._isEditable = renamingEnabled
        self.setMenuItemEnabled("Rename", renamingEnabled)

    def onRename(self):
        """
        处理引脚重命名事件。
        弹出输入对话框获取新的引脚名称，若名称有效则设置为唯一名称，并更新节点布局和形状。
        """
        name, confirmed = QInputDialog.getText(None, "Rename", "Enter new pin name")
        if confirmed and name != self.name and name != "":
            uniqueName = self._rawPin.owningNode().getUniqPinName(name)
            self.setName(uniqueName)
            self.setDisplayName(uniqueName)
            self.owningNode().invalidateNodeLayouts()
            self.owningNode().updateNodeShape()

    def syncDynamic(self):
        """
        同步引脚动态删除功能的启用状态。
        根据核心引脚的动态选项启用状态，设置上下文菜单中 "Remove" 动作的启用状态。
        """
        self.setMenuItemEnabled(
            "Remove", self._rawPin.optionEnabled(PinOptions.Dynamic)
        )

    @property
    def structureType(self):
        """
        获取引脚的结构类型，通过核心引脚的 structureType 属性获取。

        :return: 引脚的结构类型
        """
        return self._rawPin.structureType

    @property
    def dirty(self):
        """
        获取引脚的脏数据状态，通过核心引脚的 dirty 属性获取。

        :return: 如果引脚为脏数据返回 True，否则返回 False
        """
        return self._rawPin.dirty

    @dirty.setter
    def dirty(self, value):
        """
        设置引脚的脏数据状态，通过核心引脚的 dirty 属性设置。

        :param value: 要设置的脏数据状态
        """
        self._rawPin.dirty = value

    def resetToDefault(self):
        """
        将引脚数据重置为默认值。
        调用 setData 方法设置为默认值。
        """
        self.setData(self.defaultValue())

    def defaultValue(self):
        """
        获取引脚的默认值，通过核心引脚的 defaultValue 方法获取。

        :return: 引脚的默认值
        """
        return self._rawPin.defaultValue()

    def currentData(self):
        """
        获取引脚的当前数据，通过核心引脚的 currentData 方法获取。

        :return: 引脚的当前数据
        """
        return self._rawPin.currentData()

    @property
    def name(self):
        """
        获取引脚的名称，通过核心引脚的 name 属性获取。

        :return: 引脚的名称
        """
        return self._rawPin.name

    def getFullName(self):
        """
        获取引脚的完整名称，通过核心引脚的 getFullName 方法获取。

        :return: 引脚的完整名称
        """
        return self._rawPin.getFullName()

    def hasConnections(self):
        """
        判断引脚是否有连接，通过核心引脚的 hasConnections 方法判断。

        :return: 如果引脚有连接返回 True，否则返回 False
        """
        return self._rawPin.hasConnections()

    def setClean(self):
        """
        将引脚设置为干净状态，通过核心引脚的 setClean 方法设置。
        """
        self._rawPin.setClean()

    def setDirty(self):
        """
        将引脚设置为脏数据状态，通过核心引脚的 setDirty 方法设置。
        """
        self._rawPin.setDirty()

    @property
    def _data(self):
        """
        获取引脚的数据，通过核心引脚的 _data 属性获取。

        :return: 引脚的数据
        """
        return self._rawPin._data

    @_data.setter
    def _data(self, value):
        """
        设置引脚的数据，通过核心引脚的 _data 属性设置。

        :param value: 要设置的数据值
        """
        self._rawPin._data = value

    @property
    def affects(self):
        """
        获取受该引脚影响的引脚列表，通过核心引脚的 affects 属性获取。

        :return: 受该引脚影响的引脚列表
        """
        return self._rawPin.affects

    @property
    def direction(self):
        """
        获取引脚的方向，通过核心引脚的 direction 属性获取。

        :return: 引脚的方向
        """
        return self._rawPin.direction

    @property
    def affected_by(self):
        """
        获取影响该引脚的引脚列表，通过核心引脚的 affected_by 属性获取。

        :return: 影响该引脚的引脚列表
        """
        return self._rawPin.affected_by

    def supportedDataTypes(self):
        """
        获取引脚支持的数据类型列表，通过核心引脚的 supportedDataTypes 方法获取。

        :return: 引脚支持的数据类型列表
        """
        return self._rawPin.supportedDataTypes()

    @property
    def connections(self):
        """
        获取引脚的 UI 连接列表。

        :return: 引脚的 UI 连接列表
        """
        return self.uiConnectionList

    @property
    def uid(self):
        """
        获取引脚的唯一标识符，通过核心引脚的 _uid 属性获取。

        :return: 引脚的唯一标识符
        """
        return self._rawPin._uid

    @uid.setter
    def uid(self, value):
        """
        设置引脚的唯一标识符，通过核心引脚的 _uid 属性设置。

        :param value: 要设置的唯一标识符
        """
        self._rawPin._uid = value

    def color(self):
        """
        获取引脚的颜色。

        :return: 引脚的颜色
        """
        return self._pinColor

    def setName(self, newName, force=False):
        """
        设置引脚的名称，通过核心引脚的 setName 方法设置。

        :param newName: 要设置的新名称
        :param force: 是否强制设置，默认为 False
        :return: 设置结果
        """
        return self._rawPin.setName(newName, force=force)

    def setData(self, value):
        """
        设置引脚的数据，通过核心引脚的 setData 方法设置，并发出 dataBeenSet 信号。

        :param value: 要设置的数据值
        """
        self._rawPin.setData(value)
        self.dataBeenSet.emit(value)

    def getData(self):
        """
        获取引脚的数据，通过核心引脚的 getData 方法获取。

        :return: 引脚的数据
        """
        return self._rawPin.getData()

    def call(self):
        """
        调用核心引脚的 call 方法。
        """
        self._rawPin.call()

    def kill(self, *args, **kwargs):
        """
        处理引脚销毁事件。
        该方法在核心引脚被删除后调用，从场景中移除引脚并更新节点形状。

        :param args: 可变参数
        :param kwargs: 关键字参数
        """
        scene = self.scene()
        if scene is None:
            del self
            return

        if self._rawPin.direction == PinDirection.Input:
            self.owningNode().inputsLayout.removeItem(self)
        else:
            self.owningNode().outputsLayout.removeItem(self)

        self.OnPinDeleted.emit(self)
        try:
            scene = self.scene()
            if scene is None:
                del self
                return
            scene.removeItem(self)
            self.owningNode().updateNodeShape()
        except:
            pass

    def assignRawPin(self, rawPin):
        """
        分配新的核心引脚。
        如果新的核心引脚与当前不同，则更新核心引脚引用，并更新引脚颜色。

        :param rawPin: 要分配的新核心引脚
        """
        if rawPin is not self._rawPin:
            self._rawPin = rawPin
            self.call = rawPin.call
            self._rawPin.setWrapper(self)
            self._pinColor = QtGui.QColor(*self._rawPin.color())

    def serializationHook(self, *args, **kwargs):
        """
        序列化钩子方法，用于在序列化时保存引脚的相关数据。

        :param args: 可变参数
        :param kwargs: 关键字参数
        :return: 序列化数据字典
        """
        data = {}
        data["bLabelHidden"] = self.bLabelHidden
        data["displayName"] = self.displayName()
        wiresData = {}
        for wire in self.uiConnectionList:
            wiresData[wire.destination().pinIndex] = wire.serialize()
        data["wires"] = wiresData
        return data

    @property
    def pinIndex(self):
        """
        获取引脚的索引，通过核心引脚的 pinIndex 属性获取。

        :return: 引脚的索引
        """
        return self._rawPin.pinIndex

    def serialize(self):
        """
        序列化引脚，通过核心引脚的 serialize 方法实现。

        :return: 序列化后的引脚数据
        """
        return self._rawPin.serialize()

    def getContainer(self):
        """
        获取引脚的容器，通过 _container 属性获取。

        :return: 引脚的容器
        """
        return self._container

    def isExec(self):
        """
        判断引脚是否为执行引脚，通过核心引脚的 isExec 方法判断。

        :return: 如果引脚为执行引脚返回 True，否则返回 False
        """
        return self._rawPin.isExec()

    @property
    def dataType(self):
        """
        获取引脚的数据类型，通过核心引脚的 dataType 属性获取。

        :return: 引脚的数据类型
        """
        return self._rawPin.dataType

    def sizeHint(self, which, constraint):
        """
        获取引脚部件的大小提示。
        根据字体和显示名称计算部件的宽度和高度。

        :param which: 大小提示类型
        :param constraint: 大小约束
        :return: 部件的大小提示
        """
        height = QtGui.QFontMetrics(self._font).height()
        width = self.pinSize * 2
        if not self.bLabelHidden:
            width += QtGui.QFontMetrics(self._font).horizontalAdvance(
                self.displayName()
            )
        return QtCore.QSizeF(width, height)

    def shape(self):
        """
        获取引脚的形状，通过添加一个椭圆到路径中实现。

        :return: 引脚的形状路径
        """
        path = QtGui.QPainterPath()
        path.addEllipse(self.boundingRect())
        return path

    def isArray(self):
        """
        判断引脚是否为数组类型，通过核心引脚的 isArray 方法判断。

        :return: 如果引脚为数组类型返回 True，否则返回 False
        """
        return self._rawPin.isArray()

    def isDict(self):
        """
        判断引脚是否为字典类型，通过核心引脚的 isDict 方法判断。

        :return: 如果引脚为字典类型返回 True，否则返回 False
        """
        return self._rawPin.isDict()

    def paint(self, painter, option, widget):
        """
        绘制引脚。
        根据引脚的类型（数组、字典或值）调用不同的绘制方法。

        :param painter: 绘制器对象
        :param option: 绘制选项
        :param widget: 绘制的部件
        """
        if self.isArray():
            PinPainter.asArrayPin(self, painter, option, widget)
        elif self.isDict():
            PinPainter.asDictPin(self, painter, option, widget)
        else:
            PinPainter.asValuePin(self, painter, option, widget)

    def contextMenuEvent(self, event):
        """
        处理上下文菜单事件，弹出上下文菜单。

        :param event: 上下文菜单事件对象
        """
        self.menu.exec_(event.screenPos())

    def getLayout(self):
        """
        获取引脚所在的布局。
        根据引脚的方向返回输入或输出布局。

        :return: 引脚所在的布局
        """
        if self.direction == PinDirection.Input:
            return self.owningNode().inputsLayout
        else:
            return self.owningNode().outputsLayout

    @property
    def description(self):
        """
        获取引脚的描述，通过核心引脚的 description 属性获取。

        :return: 引脚的描述
        """
        return self._rawPin.description

    @description.setter
    def description(self, value):
        """
        设置引脚的描述，通过核心引脚的 description 属性设置，并更新工具提示。

        :param value: 要设置的描述
        """
        self._rawPin.description = value
        self.setToolTip(self._rawPin.description)

    def hoverEnterEvent(self, event):
        """
        处理鼠标悬停进入事件，更新部件并标记为悬停状态。

        :param event: 鼠标悬停进入事件对象
        """
        super(UIPinBase, self).hoverEnterEvent(event)
        self.update()
        self.hovered = True
        event.accept()

    def hoverLeaveEvent(self, event):
        """
        处理鼠标悬停离开事件，更新部件并标记为非悬停状态。

        :param event: 鼠标悬停离开事件对象
        """
        super(UIPinBase, self).hoverLeaveEvent(event)
        self.update()
        self.hovered = False

    def pinConnected(self, other):
        """
        处理引脚连接事件，发出 OnPinConnected 信号并更新部件。

        :param other: 连接的另一个引脚
        """
        self.OnPinConnected.emit(other)
        self.update()

    def pinDisconnected(self, other):
        """
        处理引脚断开连接事件，发出 OnPinDisconnected 信号并更新部件。

        :param other: 断开连接的另一个引脚
        """
        self.OnPinDisconnected.emit(other)
        self.update()

    def selectStructure(self):
        """
        处理选择引脚结构类型的事件。
        弹出输入对话框让用户选择结构类型，若选择有效则更新核心引脚的结构类型。
        """
        item, ok = QInputDialog.getItem(
            None, "", "", ([i.name for i in list(StructureType)]), 0, False
        )
        if ok and item:
            self._rawPin.changeStructure(StructureType[item], True)


class PinGroup(UIPinBase):
    """
    引脚组类，继承自 UIPinBase，用于管理一组引脚。

    该类提供了引脚组的基本功能，如添加引脚、展开和折叠等。
    """

    def __init__(self, owningNode, direction, name="groupName"):
        """
        初始化引脚组实例。

        :param owningNode: 拥有该引脚组的节点
        :param direction: 引脚组的方向
        :param name: 引脚组的名称，默认为 "groupName"
        """
        self._name = name
        self._direction = direction
        super(PinGroup, self).__init__(owningNode, None)
        # 标记引脚组是否展开
        self.expanded = True
        # 存储引脚组中的引脚列表
        self._pins = list()

    @property
    def description(self):
        """
        获取引脚组的描述。

        :return: 引脚组的描述
        """
        return "Pin group desc"

    def numPins(self):
        """
        获取引脚组中的引脚数量。

        :return: 引脚组中的引脚数量
        """
        return len(self._pins)

    def kill(self):
        """
        处理引脚组销毁事件。
        从场景和节点布局中移除引脚组，并更新节点形状。
        """
        scene = self.scene()
        if scene is None:
            del self
            return

        if self._direction == PinDirection.Input:
            self.owningNode().inputsLayout.removeItem(self)
            self.owningNode().groups["input"].pop(self.name)
        else:
            self.owningNode().outputsLayout.removeItem(self)
            self.owningNode().groups["output"].pop(self.name)

        # self.OnPinDeleted.emit(self)
        try:
            scene = self.scene()
            if scene is None:
                del self
                return
            scene.removeItem(self)
            self.owningNode().updateNodeShape()
        except:
            pass

    def setExpanded(self, expanded):
        """
        设置引脚组的展开状态。
        根据展开状态设置引脚的可见性和连接线的位置。

        :param expanded: 是否展开
        """
        self.expanded = expanded
        for pin in self._pins:
            pin.setVisible(self.expanded)
            if pin.direction == PinDirection.Input:
                for wire in pin.uiConnectionList:
                    if expanded:
                        wire.destinationPositionOverride = None
                    else:
                        wire.destinationPositionOverride = lambda: self.scenePos() + QtCore.QPointF(
                            0, self.geometry().height()
                        )

            if pin.direction == PinDirection.Output:
                for wire in pin.uiConnectionList:
                    if expanded:
                        wire.sourcePositionOverride = None
                    else:
                        wire.sourcePositionOverride = lambda: self.scenePos() + QtCore.QPointF(
                            self.geometry().width(), self.geometry().height()
                        )

        self.update()
        self.owningNode().update()

    def onChildKilled(self, uiPin):
        """
        处理引脚组中引脚被删除的事件。
        从引脚列表中移除被删除的引脚，若引脚组为空则销毁引脚组。

        :param uiPin: 被删除的引脚
        """
        self._pins.remove(uiPin)
        if len(self._pins) == 0:
            self.kill()

    def addPin(self, uiPin):
        """
        向引脚组中添加引脚。
        将引脚添加到引脚列表中，并连接引脚的删除信号到 onChildKilled 方法。

        :param uiPin: 要添加的引脚
        """
        self._pins.append(uiPin)
        uiPin.OnPinDeleted.connect(self.onChildKilled)

    def onClick(self):
        """
        处理引脚组点击事件。
        切换引脚组的展开状态。
        """
        self.setExpanded(not self.expanded)

    def contextMenuEvent(self, event):
        """
        处理引脚组的上下文菜单事件，此处为空实现。

        :param event: 上下文菜单事件对象
        """
        pass

    @property
    def name(self):
        """
        获取引脚组的名称。

        :return: 引脚组的名称
        """
        return self._name

    @name.setter
    def name(self, newName):
        """
        设置引脚组的名称。
        更新节点中引脚组的名称映射。

        :param newName: 要设置的新名称
        """
        if self._direction == PinDirection.Input:
            self.owningNode().groups["input"][newName] = (
                self.owningNode().groups["input"].pop(self._name)
            )
        else:
            self.owningNode().groups["output"][newName] = (
                self.owningNode().groups["output"].pop(self._name)
            )
        self._name = newName

    def hoverEnterEvent(self, event):
        """
        处理鼠标悬停进入引脚组事件，更新部件和节点状态。

        :param event: 鼠标悬停进入事件对象
        """
        super(QGraphicsWidget, self).hoverEnterEvent(event)
        self.hovered = True
        self.update()
        self.owningNode().update()
        event.accept()

    def hoverLeaveEvent(self, event):
        """
        处理鼠标悬停离开引脚组事件，更新部件和节点状态。

        :param event: 鼠标悬停离开事件对象
        """
        super(QGraphicsWidget, self).hoverLeaveEvent(event)
        self.hovered = False
        self.update()
        self.owningNode().update()
        event.accept()

    def sizeHint(self, which, constraint):
        """
        获取引脚组部件的大小提示。
        根据字体和引脚组名称计算部件的宽度和高度。

        :param which: 大小提示类型
        :param constraint: 大小约束
        :return: 部件的大小提示
        """
        height = QtGui.QFontMetrics(self._font).height()
        width = (
                QtGui.QFontMetrics(self._font).horizontalAdvance(self.name) + self.pinSize
        )
        return QtCore.QSizeF(width, height)

    def paint(self, painter, option, widget):
        """
        绘制引脚组。
        绘制引脚组的背景、名称和展开/折叠图标。

        :param painter: 绘制器对象
        :param option: 绘制选项
        :param widget: 绘制的部件
        """
        frame = QtCore.QRectF(QtCore.QPointF(0, 0), self.geometry().size())
        frame = frame.translated(self.pinSize * 1.1, 0)
        bgRect = QtCore.QRectF(frame)
        bgRect.setX(0)
        painter.setFont(self._font)
        painter.setPen(QtGui.QPen(self.labelColor, 1.0))
        painter.drawText(frame, self.name)

        painter.setPen(QtGui.QPen(self.labelColor, 0.1))
        square = QtCore.QRectF(
            QtCore.QPointF(0, 0), QtCore.QSizeF(self.pinSize / 1.1, self.pinSize / 1.1)
        )
        square2 = square.translated(0, (self.pinSize / 1.1) / 3)
        painter.drawRect(square2)

        font = QtGui.QFont(self._font)
        font.setPixelSize(7)
        painter.setFont(font)
        if not self.expanded:
            x = QtGui.QFontMetrics(font).horizontalAdvance("+")
            square = square.translated(x / 3, 0.5)
            painter.drawText(square, "+")
        else:
            x = QtGui.QFontMetrics(font).horizontalAdvance("-")
            square = square.translated(x / 3, 0.5)
            painter.drawText(square, "-")


def REGISTER_UI_PIN_FACTORY(packageName, factory):
    """
    注册 UI 引脚的工厂函数。
    如果包名未在 UI_PINS_FACTORIES 字典中，则将工厂函数添加到该字典中。

    :param packageName: 包名
    :param factory: 工厂函数
    """
    if packageName not in UI_PINS_FACTORIES:
        UI_PINS_FACTORIES[packageName] = factory


def getUIPinInstance(owningNode, raw_instance):
    """
    根据包名获取 UI 引脚实例。
    如果包名存在于 UI_PINS_FACTORIES 字典中，则使用对应的工厂函数创建实例；否则返回 UIPinBase 实例。

    :param owningNode: 拥有该引脚的节点
    :param raw_instance: 核心引脚实例
    :return: UI 引脚实例
    """
    packageName = raw_instance.packageName
    if packageName in UI_PINS_FACTORIES:
        return UI_PINS_FACTORIES[packageName](owningNode, raw_instance)
    else:
        return UIPinBase(owningNode, raw_instance)